package fid

import (
	"fmt"
	"reflect"
	"strings"

	"gitee.com/gaoguangting/fid/session"
	"github.com/labstack/echo"
	"github.com/sirupsen/logrus"
)

type RoutePrefixer interface {
	RouterPrefix() string
}

type Authorization interface {
	Authorization() map[string][]string
}

type ControllerProto struct {
	Prefix             string                    // controller前缀
	Type               reflect.Type              // controller类型 (必须为Struct)
	RouterParam        map[string]reflect.Type   // 参数类型
	RouterMap          map[string]reflect.Method // 函数类型
	RouterReturns      map[string]reflect.Type   // 返回值第一个类型
	ContainEchoContext bool
	ContainUserInfo    bool

	sessionManager *session.Manager
	fid            *Fid
}

// UserLogin -> user/login
func nameToPath(name string) string {
	return camelSlipt(name, "/")
}

func nameToTarin(name string) string {
	return camelSlipt(name, "_")
}

func camelSlipt(name string, seq string) string {
	if len(name) < 2 {
		return name
	}
	temp := strings.ToLower(name[0:1])
	for i := 1; i < len(name); i++ {
		if string(name[i:i+1]) >= "A" && string(name[i:i+1]) <= "Z" {
			temp += seq
			temp += strings.ToLower(string(name[i : i+1]))
		} else {
			temp += string(name[i : i+1])
		}
	}
	return temp
}

func newProto(controller interface{}) *ControllerProto {
	typ := reflect.TypeOf(controller)
	if typ.Kind() != reflect.Ptr && typ.Elem().Kind() != reflect.Struct {
		panic("controller类型错误")
	}
	proto := &ControllerProto{
		RouterParam:   make(map[string]reflect.Type),
		RouterMap:     make(map[string]reflect.Method),
		RouterReturns: make(map[string]reflect.Type),
	}
	if rp, ok := controller.(RoutePrefixer); ok {
		proto.Prefix = rp.RouterPrefix()
	} else {
		typeName := typ.Name()
		if typ.Kind() == reflect.Ptr {
			typeName = typ.Elem().Name()
		}
		if strings.HasSuffix(typeName, "Controller") {
			typeName = string(typeName[0 : len(typeName)-len("Controller")])
		}
		proto.Prefix = nameToPath(typeName)
	}
	if !strings.HasPrefix(proto.Prefix, "/") {
		proto.Prefix = "/" + proto.Prefix
	}
	proto.Type = typ
	return proto
}

func (this *ControllerProto) installRouter() {
	fmt.Printf("[ROUTE] ------------- %v --------------\n", this.Prefix)
	for i := 0; i < this.Type.NumMethod(); i++ {
		method := this.Type.Method(i)
		if strings.HasPrefix(method.Name, RoutePrefix) {
			pathName := nameToPath(string(method.Name[len(RoutePrefix):]))
			pathName = strings.TrimLeft(pathName, "/")
			pathName = this.Prefix + "/" + pathName
			if (method.Type.NumIn() != 1 && method.Type.NumIn() != 2) &&
				(method.Type.NumOut() != 1 && method.Type.NumOut() != 2) {
				logrus.WithFields(logrus.Fields{
					"Controller": this.Type.Name(),
					"Method":     method.Name,
				}).Error("参数个数不合要求，不能注册路由")
				continue
			}
			if method.Type.NumIn() == 2 {
				this.RouterParam[pathName] = method.Type.In(1)
			}
			if method.Type.NumOut() == 2 {
				this.RouterReturns[pathName] = method.Type.Out(0)
			}
			var paramOutLastType reflect.Type
			if method.Type.NumOut() == 2 {
				paramOutLastType = method.Type.Out(1)
			} else {
				paramOutLastType = method.Type.Out(0)
			}
			// error判断
			errorType := reflect.TypeOf(new(APIError)).Elem()
			if !paramOutLastType.Implements(errorType) {
				logrus.WithFields(logrus.Fields{
					"paramOutLastType": paramOutLastType,
					"Controller":       this.Type.Name(),
					"Method":           method.Name,
				}).Error("返回类型不合要求，不能注册路由")
				continue
			}
			this.RouterMap[pathName] = method
			fmt.Printf("\t%v\n", pathName)
		}
	}
}

func (this *ControllerProto) Route(c echo.Context, sessionStroe session.Store, path string, data map[string]interface{}) error {
	method, ok := this.RouterMap[path]
	if !ok {
		return ResponseError(c, ErrBadParam)
	}
	paramsMap, ok := data["param"]
	if !ok {
		paramsMap = map[string]interface{}{}
	}
	// call method
	typ := this.Type
	val := reflect.New(this.Type.Elem())
	theController, err := this.SetController(c, sessionStroe, data, typ, val, method.Name)
	if err.Fail() {
		return ResponseError(c, err)
	}

	if err := this.HookBefore(val, theController); err.Fail() {
		return ResponseError(c, err)
	}

	defer func() {
		if panicErr := recover(); panicErr != nil {
			if fe, ok := panicErr.(FrameworkError); ok {
				if fe.PanicError == PanicErrorAuth {
					if fe.Params == nil {
						fe.Params = []string{}
					}
					htterr := NewAPIError(fmt.Sprintf("%v [请检查你是否具备以下权限%v]", ErrAuth.Message(), strings.Join(fe.Params, ",")), ErrAuth.Code())
					ResponseError(c, htterr)
				} else if fe.PanicError == PanicErrorUnlogin {
					ResponseError(c, ErrUnlogin)
				}
			} else {
				panic(panicErr)
			}
		}
	}()

	function := val.MethodByName(method.Name)
	params := []reflect.Value{}
	if pt, ok := this.RouterParam[path]; ok {
		pv := reflect.New(pt).Elem()
		itemCopy(paramsMap, pt, pv)
		params = append(params, pv)
	}
	outs := function.Call(params)
	// response
	var httpError APIError
	var response interface{}
	if len(outs) == 1 {
		httpError = outs[0].Interface().(APIError)
	} else if len(outs) == 2 {
		httpError = outs[1].Interface().(APIError)
		response = outs[0].Interface()
	}
	if httpError.Fail() {
		return ResponseError(c, httpError)
	}

	if err := this.HookEnd(val); err.Fail() {
		return ResponseError(c, err)
	}

	return Response(c, response)
}

func setController(typ reflect.Type, val reflect.Value, exec func(value reflect.Value, controller Controller) APIError) (bool, APIError) {
	for i := 0; i < typ.NumField(); i++ {
		itypField := typ.Field(i)
		ival := val.Field(i)
		if itypField.Anonymous {
			if itypField.Type.AssignableTo(reflect.TypeOf(Controller{})) {
				if controller, ok := ival.Interface().(Controller); ok {
					return true, exec(ival, controller)
				}
			}
			if b, err := setController(itypField.Type, ival, exec); b {
				return b, err
			}
		}
	}
	return false, ErrNone
}

func (this *ControllerProto) SetController(c echo.Context, store session.Store,
	data map[string]interface{}, typ reflect.Type, val reflect.Value, methodName string) (*Controller, APIError) {
	if typ.Kind() == reflect.Ptr {
		if val.IsNil() {
			val.Set(reflect.New(typ.Elem()))
		}
		typ = typ.Elem()
		val = val.Elem()
	}
	var theController *Controller
	result, herr := setController(typ, val, func(value reflect.Value, controller Controller) APIError {
		controller.EchoContext = c
		controller.Session = store
		u, b, err := this.fid.checkSessionUser(store)
		if err != nil {
			fmt.Println("user session check error", err)
			return ErrBadSession
		} else if b {
			controller.SessionUser = u
		}
		controller.MethodName = methodName
		controller.RequestMap = data
		value.Set(reflect.ValueOf(controller))
		theController = &controller
		return ErrNone
	})
	if herr.Fail() {
		return nil, herr
	}
	if !result {
		logrus.Error("controller无法赋值，请检查...")
	}
	return theController, ErrNone
}

type StartRequester interface {
	StartRequest() APIError
}

type EndRequester interface {
	EndRequest() APIError
}

type PassAuthPathInter interface {
	PassAuthorizationPaths() []string
}

func (this *ControllerProto) HookBefore(val reflect.Value, controller *Controller) APIError {
	pass := false
	login := false
	if controller != nil {
		login = controller.SessionUser != nil
	}
	if psm, b := val.Interface().(PassAuthPathInter); controller != nil && b {
		if login {
			pass = true
		} else {
			method := controller.MethodName
			for _, pm := range psm.PassAuthorizationPaths() {
				if pm == method {
					pass = true
					break
				}
			}
		}
	}
	if !pass && !login {
		return ErrUnlogin
	}
	if sreq, b := val.Interface().(StartRequester); b {
		httpError := sreq.StartRequest()
		if httpError.Fail() {
			return httpError
		}
	}
	return ErrNone
}

func (this *ControllerProto) HookEnd(val reflect.Value) APIError {
	if sreq, b := val.Interface().(EndRequester); b {
		httpError := sreq.EndRequest()
		if httpError.Fail() {
			return httpError
		}
	}
	return ErrNone
}
